package com.takeshi.landlords.entity;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.takeshi.landlords.config.Dealer;
import com.takeshi.landlords.resp.SysConstant;
import com.takeshi.landlords.util.CardModel;
import com.takeshi.landlords.util.DoubleList;
import com.takeshi.landlords.util.UserState;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * ChannelGroupObj
 *
 * @author Nanase Takeshi
 * @date 2022/2/16 18:30
 */
@Slf4j
@Data
@Accessors(chain = true)
public class ChannelGroupObj implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 房间号
     */
    private Integer roomNumber;

    /**
     * 房间密码
     */
    private String pwd;

    /**
     * 房间内的用户通道组
     */
    private ChannelGroup channelGroup;

    /**
     * 是否是游戏开始了状态
     */
    private Boolean running;

    /**
     * 当前游戏的地主用户昵称
     */
    private String landlordNickname;

    /**
     * 创建房间者的用户通道ID
     */
    private ChannelId homeownerChannelId;

    /**
     * 随机出来的第一个抢地主的用户通道ID
     */
    private ChannelId firstLandGrabberChannelId;

    /**
     * 当前游戏应该出牌的用户通道ID
     */
    private ChannelId currentPlayerChannelId;

    /**
     * 当前游戏的出牌历史记录
     */
    private List<PlayHistoryObj> playHistory;

    /**
     * 当前进行游戏的用户对象集合
     */
    private DoubleList<GamePlayer> userList;

    /**
     * 三张底牌
     */
    private List<String> holeCard;

    /**
     * 当当前应该出牌的用户ID进行变更时，检测用户是否超时响应
     *
     * @param currentPlayerChannelId
     * @return
     */
    public ChannelGroupObj setCurrentPlayerChannelId(ChannelId currentPlayerChannelId) {
        if (this.running) {
            GamePlayer currentGamePlayer = this.userList.find(item -> item.getChannelId().compareTo(currentPlayerChannelId) == 0);
            if (ObjectUtil.isNull(this.channelGroup.find(currentPlayerChannelId))) {
                //该玩家已经离线了，自动pass
                this.passCard(currentGamePlayer);
                return this;
            }
            if (ObjectUtil.isNull(this.currentPlayerChannelId) || this.currentPlayerChannelId.compareTo(currentPlayerChannelId) != 0) {
                this.currentPlayerChannelId = currentPlayerChannelId;
                //对当前游戏应该出牌的用户进行出牌超时倒计时
                Channel currentChannel = this.channelGroup.find(this.currentPlayerChannelId);
                currentGamePlayer.setCountdown(new AtomicInteger(SysConstant.COUNTDOWN));
                currentChannel.eventLoop().scheduleAtFixedRate(() -> {
                    //三十秒后每隔一秒钟给用户发送信息提示用户该做出相应的回应
                    int countdown = currentGamePlayer.getCountdown().decrementAndGet();
                    if (!this.running || this.currentPlayerChannelId.compareTo(currentGamePlayer.getChannelId()) != 0) {
                        //已经有响应了，关闭定时任务
                        currentGamePlayer.getScheduledFuture().cancel(false);
                    } else if (countdown <= 0) {
                        if (CollUtil.isEmpty(playHistory) || CollUtil.reverseNew(this.playHistory).stream().limit(userList.size() - 1).allMatch(item -> item.getCardModel().equals(CardModel.PASS))) {
                            //首次出牌或其他人都PASS时自动出牌
                            List<String> collect = currentGamePlayer.getHandCardList().stream().sorted(Comparator.comparing(Dealer.SHOW_FULL_POKER_ORDER::indexOf).reversed()).limit(1).collect(Collectors.toList());
                            this.playCards(CardModel.LEAFLET, currentGamePlayer, collect, null);
                        } else {
                            //用户没有做出相应的回应则算PASS，关闭定时任务
                            this.passCard(currentGamePlayer);
                        }
                        currentGamePlayer.getScheduledFuture().cancel(false);
                    } else if (StrUtil.isNotEmpty(this.landlordNickname) && countdown <= SysConstant.COUNTDOWN_START_SEND) {
                        //不是抢地主的情况下，每隔一秒钟给用户发送信息提示用户该做出相应的回应
                        String format = StrUtil.format(SysConstant.TIMEOUT_TEXT + "hand cards is [{}]", countdown, "please show your cards.", String.join(StrUtil.SPACE, currentGamePlayer.getHandCardList()));
                        currentChannel.writeAndFlush(new TextWebSocketFrame(format));
                    } else if (StrUtil.isEmpty(this.landlordNickname) && countdown <= SysConstant.COUNTDOWN_START_SEND) {
                        //放弃成为地主
                        this.giveUpBeingLandlord(currentGamePlayer);
                    }
                }, 0, 1, TimeUnit.SECONDS);
            }
        }
        return this;
    }

    /**
     * 放弃成为地主
     *
     * @param gamePlayer
     */
    public void giveUpBeingLandlord(GamePlayer gamePlayer) {
        GamePlayer nextPlayer = this.userList.findNext(item -> item.getChannelId().compareTo(gamePlayer.getChannelId()) == 0);
        if (nextPlayer.getChannelId().compareTo(this.firstLandGrabberChannelId) == 0) {
            //都没有人想成为地主，使用随机出来的第一个抢地主的用户为地主
            GamePlayer player = this.userList.find(item -> item.getChannelId().compareTo(this.firstLandGrabberChannelId) == 0);
            //将底牌放入地主手牌中
            player.becomeLandlord(this.holeCard);
            this.userList.foreach(item -> item.setState(UserState.IN_COMPETITION.getState()));
            this.channelGroup.writeAndFlush(new TextWebSocketFrame(StrUtil.format("No one chooses to become the landlord, the system chooses [{}] to become the landlord, hole card is [{}], the landlord please play the card...", player.getNickname(), String.join(StrUtil.SPACE, this.holeCard))));
            Optional.ofNullable(this.channelGroup.find(player.getChannelId())).map(item -> item.writeAndFlush(new TextWebSocketFrame(StrUtil.format(SysConstant.TIMEOUT_TEXT + "[{}] hand cards is [{}]", SysConstant.COUNTDOWN, StrUtil.COMMA, player.getNickname(), String.join(StrUtil.SPACE, player.getHandCardList())))));
            this.setLandlordNickname(player.getNickname()).setCurrentPlayerChannelId(player.getChannelId());
        } else {
            //给下一位用户推送选择是否成为地主消息
            this.channelGroup.writeAndFlush(new TextWebSocketFrame(StrUtil.format("[{}] Give up being a landlord, it's [{}]'s turn to choose whether to become a landlord...", gamePlayer.getNickname(), nextPlayer.getNickname())));
            Optional.ofNullable(this.channelGroup.find(nextPlayer.getChannelId())).map(item -> item.writeAndFlush(new TextWebSocketFrame(SysConstant.TO_BECOME_LANDLORD)));
            this.setCurrentPlayerChannelId(nextPlayer.getChannelId());
        }
    }

    /**
     * 过牌
     *
     * @param gamePlayer
     */
    public void passCard(GamePlayer gamePlayer) {
        GamePlayer nextPlayer = this.userList.findNext(item -> item.getChannelId().compareTo(gamePlayer.getChannelId()) == 0);
        PlayHistoryObj playHistoryObj = new PlayHistoryObj(gamePlayer.getNickname(), CardModel.PASS, null, Instant.now().toEpochMilli());
        this.channelGroup.writeAndFlush(new TextWebSocketFrame(playHistoryObj.passPlay(nextPlayer.getNickname())));
        Optional.ofNullable(this.channelGroup.find(nextPlayer.getChannelId())).map(item -> item.writeAndFlush(new TextWebSocketFrame(StrUtil.format(SysConstant.TIMEOUT_TEXT + "[{}] hand cards is [{}]", SysConstant.COUNTDOWN, StrUtil.COMMA, nextPlayer.getNickname(), String.join(StrUtil.SPACE, nextPlayer.getHandCardList())))));
        this.setCurrentPlayerChannelId(nextPlayer.getChannelId()).getPlayHistory().add(playHistoryObj);
    }

    /**
     * 退出房间
     *
     * @param gamePlayer
     * @param consumer
     */
    public void exitRoom(GamePlayer gamePlayer, Consumer<ChannelGroup> consumer) {
        this.channelGroup.writeAndFlush(new TextWebSocketFrame(StrUtil.format("[{}] player exit the room...", gamePlayer.getNickname())));
        this.channelGroup.removeIf(item -> item.id().compareTo(gamePlayer.getChannelId()) == 0);
        //取消定时任务
        ScheduledFuture<?> scheduledFuture = gamePlayer.getScheduledFuture();
        if (ObjectUtil.isNotNull(scheduledFuture)) {
            scheduledFuture.cancel(true);
        }
        if (gamePlayer.getChannelId().compareTo(this.homeownerChannelId) == 0) {
            //如果退出的是房主，则重新选择一个房主
            this.channelGroup.stream().findAny().ifPresent(item -> {
                item.writeAndFlush(new TextWebSocketFrame("you have become the new homeowner..."));
            });
        }
        gamePlayer.setState(UserState.BASE_CHOOSE.getState());
        consumer.accept(this.channelGroup);
    }

    /**
     * 出牌
     *
     * @param cardModel
     * @param gamePlayer
     * @param list
     * @param content
     */
    public void playCards(CardModel cardModel, GamePlayer gamePlayer, List<String> list, String content) {
        List<String> collect = list.stream().map(item -> StrUtil.equals("0", item) ? "10" : item.toUpperCase(Locale.ROOT)).collect(Collectors.toList());
        List<String> handCardList = new ArrayList<>(gamePlayer.getHandCardList());
        collect.removeIf(item -> handCardList.contains(item) && handCardList.remove(item));
        if (CollUtil.isNotEmpty(collect)) {
            //出的牌与手牌不符
            Optional.ofNullable(this.channelGroup.find(gamePlayer.getChannelId())).map(item -> item.writeAndFlush(new TextWebSocketFrame("出牌无效，出的牌与手牌不符~")));
            return;
        }
        List<PlayHistoryObj> historyObjList =
                Optional.ofNullable(this.playHistory)
                        .map(item -> CollUtil.reverseNew(item).stream().limit(this.userList.size() - 1).collect(Collectors.toList()))
                        .orElse(new ArrayList<>());
        PlayHistoryObj playHistoryObj = new PlayHistoryObj(gamePlayer.getNickname(), cardModel, list, Instant.now().toEpochMilli());
        boolean match = cardModel.equals(CardModel.KING_FRIED) || historyObjList.stream().allMatch(item -> item.getCardModel().equals(CardModel.PASS));
        //王炸，最大的牌，直接出牌，判断是否与其他人的牌型做比较，其他人都pass，不比较，重新定牌型
        if (match) {
            this.isGameOver(list, playHistoryObj);
            return;
        }
        //获取其他人的牌型
        PlayHistoryObj historyObj = historyObjList.stream().filter(item -> !item.getCardModel().equals(CardModel.PASS)).findFirst().orElseThrow(RuntimeException::new);
        boolean biggerCard = Dealer.isBiggerCard(gamePlayer.getHandCardList(), cardModel, historyObj.getCardModel(), list, historyObj.getCardList());
        //是否大过上一个有效的牌
        if (biggerCard) {
            //从手牌中移除当前出的牌，判断是否游戏结束
            this.isGameOver(list, playHistoryObj);
            return;
        }
        Optional.ofNullable(this.channelGroup.find(gamePlayer.getChannelId())).map(item -> item.writeAndFlush(new TextWebSocketFrame(StrUtil.format("出牌无效，无法大过上家出的：[{}]，可输入 v 查看出牌历史~", String.join(StrUtil.SPACE, historyObj.getCardList())))));
        this.channelGroup.writeAndFlush(new TextWebSocketFrame(StrUtil.format("[{}] say: {}", gamePlayer.getNickname(), content)));
    }

    /**
     * 从手牌中移除当前出的牌，判断是否游戏结束
     */
    public void isGameOver(List<String> list, PlayHistoryObj playHistoryObj) {
        GamePlayer gamePlayer = this.userList.find(item -> item.getChannelId().compareTo(this.currentPlayerChannelId) == 0);
        GamePlayer nextPlayer = this.userList.findNext(item -> item.getChannelId().compareTo(this.currentPlayerChannelId) == 0);
        List<String> handCardList = gamePlayer.getHandCardList();
        //从手牌中移除当前出的牌
        List<String> collect = list.stream().map(item -> StrUtil.equals("0", item) ? "10" : item.toUpperCase(Locale.ROOT)).collect(Collectors.toList());
        handCardList.removeIf(item -> collect.contains(item) && collect.remove(item));
        this.playHistory = CollUtil.defaultIfEmpty(this.playHistory, new ArrayList<>());
        this.playHistory.add(playHistoryObj);
        //判断是否游戏结束
        if (CollUtil.isEmpty(handCardList)) {
            this.running = false;
            this.landlordNickname = null;
            this.currentPlayerChannelId = null;
            this.playHistory = null;
            this.userList.foreach(GamePlayer::gameOver);
            //获取房主信息
            GamePlayer homeowner = this.userList.find(item -> item.getChannelId().compareTo(this.homeownerChannelId) == 0);
            this.channelGroup.write(new TextWebSocketFrame(playHistoryObj.playInfo(nextPlayer.getNickname(), handCardList.size())));
            this.channelGroup.writeAndFlush(new TextWebSocketFrame(StrUtil.format("Game Over, and the winner is [{}], The homeowner [{}] enter [S] to restart the game~", gamePlayer.getNickname(), homeowner.getNickname())));
        } else {
            this.channelGroup.writeAndFlush(new TextWebSocketFrame(playHistoryObj.playInfo(nextPlayer.getNickname(), handCardList.size())));
            String format = StrUtil.format(SysConstant.TIMEOUT_TEXT + "hand cards is [{}]", SysConstant.COUNTDOWN, "please show your cards.", String.join(StrUtil.SPACE, nextPlayer.getHandCardList()));
            Optional.ofNullable(this.channelGroup.find(nextPlayer.getChannelId())).map(item -> item.writeAndFlush(new TextWebSocketFrame(format)));
        }
        //使用set方法设置值，上面需要监听currentPlayerChannelId改变
        this.setCurrentPlayerChannelId(nextPlayer.getChannelId());
    }

}

